/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019.
 * Description: add llc flush feature when decompress kernel
 * Author: xiaowei
 * Create: 2018-10-15
 */
#include "base.h"
#include "libfdt.h"
#include <linux/of.h>
#include <linux/kernel.h>
#include <linux/serial_core.h>

static u32 uart_iotype;
unsigned int early_print_disabled;

/*
 * UART_TYPE_PL011 is index of "arm,pl011", and defined in arch/arm64/kernel/debug.S
 * remember to modify it if uart_node_matches changed
 */
static const struct of_device_id uart_node_matches[] = {
	{ .compatible = "snps,dw-apb-uart", },
	{ .compatible = "arm,pl011", },
	{ .compatible = "fsl,ns16550", },
	{ .compatible = "ns16550", },
	{}
};

static int get_addr_cells(const void * const dtb_address, int offset)
{
	const unsigned int *data = NULL;
	int parent;

	do {
		parent = fdt_parent_offset(dtb_address, offset);
		if (parent >= 0)
			offset = parent;

		data = fdt_getprop(dtb_address, offset, "#address-cells", NULL);
		if (data)
			return fdt32_to_cpu(*data);
	} while (parent >= 0);

	return OF_ROOT_NODE_ADDR_CELLS_DEFAULT;
}

static int match_str_in_bootargs(const char *bootargs, const char *target)
{
	const char *str = bootargs;

	while (*str) {
		if (strncmp(str, target, strlen(target)) == 0) {
			if ((str != bootargs) && (*(str - sizeof(char)) != ' ')) {
				str++;
				continue;
			}
			if ((*(str + strlen(target)) != ' ') &&	(*(str + strlen(target)) != '\0')) {
				str++;
				continue;
			}
			return 1;
		}
		str++;
	}
	return 0;
}

void find_early_print_disabled(void *dtb_address)
{
	int node;
	char *bootargs;
	int size;

	early_print_disabled = 0;
	node = fdt_subnode_offset(dtb_address, 0, "chosen");
	if (node < 0)
		return;

	bootargs = (char *) fdt_getprop(dtb_address, node, "bootargs", &size);
	if (bootargs == NULL)
		return;

	if (match_str_in_bootargs(bootargs, "early_print_disabled"))
		early_print_disabled = 1;
}

#ifdef CONFIG_RTOS_HAL_DEBUG_LL
void find_debugll(void *dtb_address)
{
	int node;
	char *bootargs;
	int size;

	debug_ll = 0;
	node = fdt_subnode_offset(dtb_address, 0, "chosen");
	if (node < 0) {
		putstr("DTS: find chosen node failed\n");
		return;
	}

	bootargs = (char *) fdt_getprop(dtb_address, node, "bootargs", &size);
	if (bootargs == NULL) {
		putstr("DTS: find bootargs failed\n");
		return;
	}

	if (match_str_in_bootargs(bootargs, "debug_ll=on"))
		debug_ll = 1;
}
#endif

static void find_uart_info(void *dtb_address)
{
	int i;
	int node;
	int uart_reg_shift;
	const void *data = NULL;
	unsigned int n_addr_cells;

	uart_iotype = UPIO_MEM;

	for (i = 0; i < ARRAY_SIZE(uart_node_matches); i++) {
		node = fdt_node_offset_by_compatible(dtb_address, -1, uart_node_matches[i].compatible);
		if (node >= 0)
			break;
	}

	if (i == ARRAY_SIZE(uart_node_matches))
		return;

#ifdef CONFIG_RTOS_HAL_DEBUG_LL
	uart_type = i;
#endif
	data = fdt_getprop(dtb_address, node, "reg", NULL);
	if (data) {
		n_addr_cells = get_addr_cells(dtb_address, node);
		if (n_addr_cells == 1)
			uart_addr = fdt32_to_cpu(*(fdt32_t *)data);
		else if (n_addr_cells == 2)
			uart_addr = fdt64_to_cpu(*(fdt64_t *)data);
	}

	data = fdt_getprop(dtb_address, node, "reg-shift", NULL);
	if (data) {
		uart_reg_shift = fdt32_to_cpu(*(fdt32_t *)data);
		if (uart_reg_shift == 2)
			uart_iotype = UPIO_MEM32;
	}
}

void find_info_from_dtb(void *dtb_address)
{
	find_uart_info(dtb_address);
}

void writew(long addr, int val)
{
	*(int *)(addr) = le32_to_cpu(val);
}

unsigned int readw(long addr)
{
	return le32_to_cpu(*(int *)(addr));
}

void putc(const char c)
{
	volatile int i = 0;

	switch (uart_iotype) {
	case UPIO_MEM:
		*(volatile u8 *)uart_addr = (u8)c;
		break;
	case UPIO_MEM32:
		*(volatile u32 *)uart_addr = le32_to_cpu((u32)c);
		break;
	default:
		break;
	}

	while (i++ < 0x100000)
	;
}

void putstr(const char *ptr)
{
	char c;
	if (early_print_disabled)
		return;

	while ((c = *ptr++) != '\0') {
		if (c == '\n')
			putc('\r');
		putc(c);
	}
}

void print_number(const unsigned long num)
{
	unsigned int i;
	unsigned int k;
	unsigned char a[19];

	a[0] = '0';
	a[1] = 'x';
	for (k = 17, i = 0; i < 16; i++, k--) {
		unsigned char uc_num_temp;

		uc_num_temp = (unsigned char)((num >> (i * 4)) & 0x0f);
		uc_num_temp += 0x30;
		if (uc_num_temp >= 0x3a)
			uc_num_temp += 7;
		a[k] = uc_num_temp;
	}
	a[18] = 0;

	putstr(a);
	putstr("\n");
}
